#ifndef XG_FILE_CPP
#define XG_FILE_CPP
///////////////////////////////////////////////////////////////////
#ifdef XG_LINUX
#include <sys/stat.h>
#include <sys/file.h>
#endif

#include "../cmd.h"
#include "../File.h"

long long IFile::tell() const
{
	return XG_ERROR;
}
long long IFile::size() const
{
	return XG_ERROR;
}
long long IFile::seek(long long offset, int mode)
{
	return XG_ERROR;
}

File::~File()
{
	close();
}
void File::close()
{
	if (handle)
	{
		fclose(handle);
		handle = NULL;
	}
}
bool File::open(const string& filename, const string& mode)
{
	close();

	handle = fopen(stdx::syscode(filename).c_str(), mode.c_str());

	return handle ? true : false;
}
bool File::create(const string& filename)
{
	return open(filename, "wb+");
}
bool File::eof() const
{
	return feof(handle) ? true : false;
}
int File::read(void* data, int size)
{
	return fread(data, 1, size, handle);
}
int File::write(const void* data, int size)
{
	return fwrite(data, 1, size, handle);
}
void File::flush() const
{
	fflush(handle);
}
long long File::seek(long long offset, int mode)
{
	return fseek(handle, (long)(offset), mode);
}
long long File::tell() const
{
	return ftell(handle);
}
long long File::size() const
{
	long long sz = 0;
	long long offset = 0;

	offset = tell();

	if (const_cast<File*>(this)->seek(0, SEEK_END) >= 0)
	{
		sz = tell();
	}
	else
	{
		sz = XG_ERROR;
	}

	const_cast<File*>(this)->seek(offset, SEEK_SET);

	return sz;
}


XFile::~XFile()
{
	close();
}
void XFile::close()
{
	if (HandleCanUse(handle))
	{
		Close(handle);
		handle = INVALID_HANDLE_VALUE;
	}
}
bool XFile::unlock()
{
#ifdef XG_LINUX
	return flock(handle, LOCK_UN) == 0;
#else
	OVERLAPPED info = {0};
	
	return UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &info) == TRUE;
#endif
}
bool XFile::lock(bool waited)
{
#ifdef XG_LINUX
	if (waited)
	{
		return flock(handle, LOCK_EX) == 0;
	}
	else
	{
		return flock(handle, LOCK_EX | LOCK_NB) == 0;
	}
#else
	OVERLAPPED info = {0};
	
	if (waited)
	{
		return LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &info) == TRUE;
	}
	else
	{
		return LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD, &info) == TRUE;
	}
						
#endif
}
bool XFile::open(const string& filename, E_OPEN_MODE mode)
{
	close();

	handle = Open(filename.c_str(), mode);
	
	return HandleCanUse(handle);
}
bool XFile::create(const string& filename)
{
	return open(filename, eCREATE);
}
long long XFile::seek(long long offset, int mode)
{
	return Seek(handle, offset, mode);
}
long long XFile::tell() const
{
	return const_cast<XFile*>(this)->seek(0, SEEK_CUR);
}
long long XFile::size() const
{
	long long offset = tell();
	long long sz = const_cast<XFile*>(this)->seek(0, SEEK_END);

	return offset == const_cast<XFile*>(this)->seek(offset) ? sz : XG_ERROR;
}
void XFile::flush() const
{
	Flush(handle);
}
int XFile::read(void* data, int size)
{
	return Read(handle, data, size);
}
int XFile::write(const void* data, int size)
{
	return Write(handle, data, size);
}

MemFile::~MemFile()
{
	close();
}
void MemFile::close()
{
	maxsz = 0;
	offset = 0;
	writed = 0;
	data = NULL;
	buffer.free();
}
bool MemFile::open(SmartBuffer buffer)
{
	CHECK_FALSE_RETURN(buffer.str());

	this->offset = 0;
	this->buffer = buffer;
	this->data = buffer.str();
	this->maxsz = this->writed = buffer.size();
	
	return true;
}
bool MemFile::open(void* data, int len)
{
	CHECK_FALSE_RETURN(data && len > 0);

	this->offset = 0;
	this->data = (char*)(data);
	this->maxsz = this->writed = len;

	return true;
}
bool MemFile::create(int maxsz)
{
	this->offset = 0;
	this->writed = 0;
	this->maxsz = maxsz;
	this->data = (char*)buffer.malloc(maxsz);

	return true;
}
long long MemFile::seek(long long offset, int mode)
{
	if (writed == 0) return 0;

	if (mode == SEEK_CUR)
	{
		offset += this->offset;
	}
	else if (mode == SEEK_END)
	{
		offset = writed - offset;
	}

	if (offset < 0)
	{
		this->offset = 0;
	}
	else if (offset < writed)
	{
		this->offset = offset;
	}
	else
	{
		this->offset = writed;
	}

	return this->offset;
}
long long MemFile::tell() const
{
	return offset;
}
long long MemFile::size() const
{
	return writed;
}
int MemFile::read(void* data, int size)
{
	if (writed <= offset) return 0;

	if (offset + size > writed) size = writed - offset;

	memcpy(data, this->data + offset, size);

	offset += size;

	return size;
}
bool MemFile::truncate(int len)
{
	CHECK_FALSE_RETURN(maxsz >= len);

	if (offset > len) offset = len;

	writed = len;
	maxsz = len;
	
	return true;
}
int MemFile::write(const void* data, int size)
{
	if (offset + size > maxsz) return 0;

	memcpy(this->data + offset, data, size);

	offset += size;

	if (offset > writed) writed = offset;

	return size;
}
bool MemFile::save(const string& filename) const
{
	File file;

	CHECK_FALSE_RETURN(data && file.create(filename));
	CHECK_FALSE_RETURN(file.write(data, writed) > 0);

	file.flush();

	return true;
}
bool MemFile::load(const string& filename)
{
	File file;

	CHECK_FALSE_RETURN(file.open(filename, "rb"));

	long long sz = file.size();

	if (sz <= 0 || sz > XG_MEMFILE_MAXSZ) return false;

	SmartBuffer buffer(sz);

	CHECK_FALSE_RETURN(file.read(buffer.str(), buffer.size()) == sz);

	return open(buffer);
}

int CacheFile::getContent(const string& path, SmartBuffer& content, time_t& utime)
{
	Item item;
	time_t now = time(NULL);

	auto get = [&](){
		int len;
		XFile file;

		len = file.open(path, eREAD) ? file.size() : XG_NOTFOUND;

		if (len <= 0 || len > maxfilesize) return len;

		return file.read(item.content.malloc(len), len);
	};

	if (cachemap.get(path, item))
	{
		if (item.ctime + 3 > now)
		{
			utime = item.utime;
		}
		else
		{
			utime = path::mtime(path);

			if (utime <= 0) return XG_NOTFOUND;

			if (utime >= item.ctime)
			{
				int len = get();

				if (len <= 0 || len > maxfilesize)
				{
					cachemap.remove(path);

					return len;
				}

				cachemap.update(path, [&](Item& data){
					data.content = item.content;
					data.utime = utime;
					data.ctime = now;
				});
			}
		}

		content = item.content;

		return content.size();
	}

	utime = path::mtime(path);

	if (utime <= 0) return XG_SYSERR;

	int len = get();

	if (len <= 0 || len > maxfilesize) return len;

	content = item.content;
	item.utime = utime;
	item.ctime = now;

	cachemap.set(path, item);

	return len;
}

void TextFile::close()
{
	if (fp)
	{
		if (fp == stdout)
		{
			fflush(fp);
		}
		else
		{
			fclose(fp);
		}

		fp = NULL;
	}
}
const TextFile& TextFile::printf(const char* fmt, ...) const
{
	int len;
	string str;
	va_list args;

	va_start(args, fmt);
	len = stdx::vformat(str, fmt, args);
	va_end(args);

	append(str);

	return *this;
}

void ConfigFile::close()
{
	vec.clear();
}
bool ConfigFile::reload()
{
	return init(filepath);
}
const string& ConfigFile::getFilePath() const
{
	return filepath;
}
bool ConfigFile::save(const string& path)
{
	File file;
	string msg = toString();
	
	CHECK_FALSE_RETURN(file.open(path.empty() ? filepath : path, "w+"));
	CHECK_FALSE_RETURN(file.write(msg.c_str(), msg.length()) > 0);

	file.flush();

	return true;
}
int ConfigFile::getMapData(map<string, string>& datamap) const
{
	int len;
	string val;

	for (auto& item : vec)
	{
		val = item.val;
		len = val.length();

		if (len > 1 && ((val[0] == '\'' && val[len - 1] == '\'') || (val[0] == '\"' && val[len - 1] == '\"')))
		{
			val = val.substr(1, len - 2);
		}

		datamap[item.key] = stdx::translate(val);
	}

	return datamap.size();
}
string ConfigFile::toString() const
{
	return toString(true);
}
bool ConfigFile::init(const string& path)
{
	vec.clear();
	
	if (path.empty()) return true;

	return open(path);
}
string ConfigFile::toString(bool info) const
{
	string str;

	for (auto& item : vec)
	{
		if (info) str += item.info;
		str += item.key + " = ";
		str += item.val + "\n";
	}

	return str;
}
bool ConfigFile::open(const string& path)
{
	size_t pos;
	string str;
	string key;
	string val;
	string msg;
	ifstream file;

	close();

	file.open(stdx::syscode(path));

	while (getline(file, str))
	{
		if (str.length() < 2 || str[0] == '#' || (str[0] == '-' && str[1] == '-'))
		{
			msg += str + "\n";

			continue;
		}

		if ((pos = str.find("=")) == string::npos)
		{
			msg += str + "\n";

			continue;
		}

		key = str.substr(0, pos);
		key = stdx::trim(key, " \r\n\t;");

		if (key.empty())
		{
			msg += str + "\n";

			continue;
		}

		val = str.c_str() + pos + 1;
		val = stdx::trim(val, " \r\n\t;");

		vec.push_back(ConfigData(key, val, msg));
		msg.clear();
	}
	
	if (path[0] == '/' || path[0] == '\\' || path.find(':') != string::npos)
	{
		filepath = path;
	}
	else
	{
		filepath = Process::GetCurrentDirectory() + "/" + stdx::replace(path, "\\", "/");
	}

	file.close();
	
	return vec.size() > 0;
}
bool ConfigFile::setVariable(const string& name, int val)
{
	return setVariable(name, stdx::str(val));
}
bool ConfigFile::setVariable(const string& name, bool val)
{
	return setVariable(name, val ? "true" : "false");
}
bool ConfigFile::setVariable(const string& name, double val)
{
	return setVariable(name, stdx::str(val));
}
bool ConfigFile::setVariable(const string& name, const char* val)
{
	return setVariable(name, string(val));
}
bool ConfigFile::setVariable(const string& name, const string& val)
{
	string data = val;
	
	data = stdx::replace(data, "\r", "\\r");
	data = stdx::replace(data, "\n", "\\n");
	data = stdx::replace(data, "\t", "\\t");

	for (auto& item : vec)
	{
		if (name == item.key)
		{
			string& str = item.val;

			if (str.empty())
			{
				if (IsAmountString(data.c_str()))
				{
					str = data;
				}
				else
				{
					str = stdx::quote(data);
				}
			}
			else if (str[0] == '\'' || str[0] == '\"')
			{
				str = stdx::quote(data);
			}
			else
			{
				str = data;
			}

			return true;
		}
	}

	if (IsAmountString(data.c_str()))
	{
		vec.push_back(ConfigData(name, data));
	}
	else
	{
		vec.push_back(ConfigData(name, stdx::quote(data)));
	}

	return true;
}
string ConfigFile::getRawVariable(const string& name) const
{
	string str;

	if (getRawVariable(name, str)) return str;

	return stdx::EmptyString();
}
string ConfigFile::getVariable(const string& name) const
{
	string str;
	
	if (getVariable(name, str)) return str;

	return stdx::EmptyString();
}
bool ConfigFile::getVariable(const string& name, int& val) const
{
	string str;

	CHECK_FALSE_RETURN(getVariable(name, str));

	str = stdx::trim(str, " \r\n\t\'\"");
	val = stdx::atoi(str.c_str());

	return true;
}
bool ConfigFile::getVariable(const string& name, bool& val) const
{
	string str;

	CHECK_FALSE_RETURN(getVariable(name, str));

	str = stdx::trim(str, " \r\n\t\'\"");
	stdx::tolower(str);

	val = (str == "true" || (str != "false" && str != "null" && str != "nil" && str != "0"));

	return true;
}
bool ConfigFile::getVariable(const string& name, double& val) const
{
	string str;

	CHECK_FALSE_RETURN(getVariable(name, str));

	str = stdx::trim(str, " \r\n\t\'\"");
	val = stdx::atof(str.c_str());

	return true;
}
bool ConfigFile::getVariable(const string& name, string& val) const
{
	CHECK_FALSE_RETURN(getRawVariable(name, val));
	
	val = stdx::translate(val);

	return true;
}
bool ConfigFile::getRawVariable(const string& name, string& val) const
{
	int len = -1;

	for (auto& item : vec)
	{
		if (name == item.key)
		{
			val = item.val;
			len = val.length();
			
			if (len > 1 && ((val[0] == '\'' && val[len - 1] == '\'') || (val[0] == '\"' && val[len - 1] == '\"')))
			{
				val = val.substr(1, len - 2);
			}

			break;
		}
	}

	return len > 0;
}

LogThread* LogThread::Instance()
{
	XG_DEFINE_GLOBAL_VARIABLE(LogThread)
}

void LogThread::callback(function<void(const string&)> func)
{
	if (TaskQueue::Instance()->getThreads() > 0)
	{
		stdx::async([=](){
			this->mtx.lock();
			this->func = func;
			this->mtx.unlock();
		});
	}
	else
	{
		this->func = func;
	}
}
bool LogThread::init(const string& path, int maxsz, bool sync)
{
	if (path.empty())
	{
		logpath = Process::GetCurrentDirectory();
	}
	else
	{
		CHECK_FALSE_RETURN(path::mkdir(path));
		
		logpath = path;
	}

	maxfilesize = maxsz;

	if (sync) return true;

	CHECK_FALSE_RETURN(maxfilesize > 0);

	buffer.malloc(1024 * 1024);

	return mq.create(buffer.str(), buffer.size()) && start();
}
bool LogThread::printf(const char* fmt, ...)
{
	if (flag < 0) return true;

	int len;
	va_list args;
	char buffer[64 * 1024];

	va_start(args, fmt);
	len = vsnprintf(buffer, sizeof(buffer), fmt, args);
	va_end(args);

	CHECK_FALSE_RETURN(len > 0);

	if (len >= sizeof(buffer))
	{
		len = sizeof(buffer) - 1;
		buffer[len] = 0;
	}

	if (this->buffer.isNull()) return print(buffer, len);

	while (true)
	{
		{
			SpinLocker lk(mtx);

			if (mq.push(buffer, len) >= 0 || maxfilesize <= 0) break;
		}

		Sleep(1);
	}

	return true;
}
bool LogThread::trace(int level, const string& msg)
{
	return trace(level, "%s", msg.c_str());
}
bool LogThread::trace(int level, const char* fmt, ...)
{
	if (flag < 0 || level < this->level) return true;

	int len;
	int num;
	DateTime dt;
	va_list args;
	char buffer[64 * 1024];
	thread_local int tid = (int)(GetCurrentThreadId()) % 1000000;
	static const char* taglist[] = {"DBG", "TIP", "INF", "IMP", "ERR"};

	CHECK_FALSE_RETURN(dt.update().canUse());
	CHECK_FALSE_RETURN(level >= 0 && level <= 4);

	va_start(args, fmt);
	len = snprintf(buffer, sizeof(buffer), "[%04d%02d%02d %02d:%02d:%02d|%s|%06d] ", dt.year, dt.month, dt.mday, dt.hour, dt.min, dt.sec, taglist[level], tid);
	num = vsnprintf(buffer + len, sizeof(buffer) - len - sizeof(char), fmt, args);
	va_end(args);

	CHECK_FALSE_RETURN(num > 0);

	len += num;

	if (len + sizeof(char) >= sizeof(buffer))
	{
		len = sizeof(buffer) - sizeof(char) - 1;
	}

	buffer[len++] = '\n';
	buffer[len] = 0;

	if (this->buffer.isNull()) return print(buffer, len);

	while (true)
	{
		{
			SpinLocker lk(mtx);

			if (mq.push(buffer, len) >= 0 || maxfilesize <= 0) break;
		}

		Sleep(1);
	}

	return true;
}
void LogThread::run()
{
	while (this->buffer.isNull()) return;

	u_int32 sz;
	u_char* msg;
	static char buffer[64 * 1024];

	while (true)
	{
		mtx.lock();

		msg = mq.pop(sz);

		if (msg == NULL || sz >= sizeof(buffer))
		{
			mtx.unlock();
			Sleep(1);

			continue;
		}

		memcpy(buffer, msg, sz);
		buffer[sz] = 0;

		if (func)
		{
			try
			{
				func(string(buffer, buffer + sz));
			}
			catch(const Exception& e)
			{
				fprintf(stderr, "catch exception[%d][%s]\n", e.getErrorCode(), e.getErrorString());
			}
			catch(const exception& e)
			{
				fprintf(stderr, "catch exception[%s]\n", e.what());
			}
			catch(...)
			{
				fprintf(stderr, "catch unknown exception\n");
			}
		}

		mtx.unlock();

		print(buffer, sz);
	}
}
bool LogThread::start()
{
	return Thread::start();
}
string LogThread::getLogFilePath()
{
	int num = 0;
	int cnt = 100;
	int idx = this->index;

	auto getFileName = [&](int idx){
		string filename = logpath;

		if (filename.back() == '/')
		{
			filename += Process::GetAppname();
		}
		else
		{
			filename += "/";
			filename += Process::GetAppname();
		}

		stdx::append(filename, ".%02d.log", idx);

		return filename;
	};

	while (true)
	{
		if (++idx >= cnt) idx = 0;

		filename = getFileName(idx);

		if (path::size(filename) < maxfilesize)
		{
			this->index = idx;

			return filename;
		}

		if (++num >= cnt)
		{
			if (++index >= cnt) index = 0;

			filename = getFileName(index);

			path::remove(filename);

			return filename;
		}
	}

	return filename;
}
XFile* LogThread::getLogFile()
{
	auto reopen = [&](){
		string filepath = getLogFilePath();

		if (filename.empty())
		{
			file.close();
		}
		else
		{
			if (file.open(filepath, path::type(filepath) == eNONE ? eCREATE : eWRITE) && file.seek(0, SEEK_END) < 0) file.close();
		}
	};

	if (HandleCanUse(file.getHandle()))
	{
		long long sz = file.tell();

		if (sz < 0 || sz > maxfilesize) reopen();
	}
	else
	{
		reopen();
	}

	return &file;
}
bool LogThread::write(const char* msg, int len)
{
	XFile* file = getLogFile();

	CHECK_FALSE_RETURN(len > 0 && file && HandleCanUse(file->getHandle()));

	if (buffer.isNull())
	{
		CHECK_FALSE_RETURN(file->lock());

		file->seek(0, SEEK_END);
		file->write(msg, len);
		file->unlock();

		return true;
	}

	return file->write(msg, len) > 0;
}
bool LogThread::print(const char* msg, int len)
{
	if (logpath.empty() || flag == 1)
	{
		fwrite(msg, 1, len, stdout);
		fflush(stdout);

		return true;
	}

	if (flag == 0) return write(msg, len);

	fwrite(msg, 1, len, stdout);
	fflush(stdout);

	return write(msg, len);
}
///////////////////////////////////////////////////////////////////
#endif